package cn.imatu.framework.data.mate.fieldsensitive;

import cn.hutool.core.util.ClassUtil;
import cn.imatu.framework.data.mate.annotations.FieldSensitive;
import cn.imatu.framework.data.mate.fieldsensitive.enums.StrategyType;
import cn.imatu.framework.data.mate.fieldsensitive.model.SensitiveClassCache;
import cn.imatu.framework.exception.BizException;
import cn.imatu.framework.tool.core.ReflectUtils;
import cn.imatu.framework.tool.core.StringUtils;
import cn.imatu.framework.tool.core.reflection.DefaultReflectorFactory;
import cn.imatu.framework.tool.core.reflection.MetaObject;
import cn.imatu.framework.tool.core.reflection.factory.DefaultObjectFactory;
import cn.imatu.framework.tool.core.reflection.wrapper.DefaultObjectWrapperFactory;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 敏感信息处理器
 *
 * @author shenguangyang
 */
@Component
@Slf4j
public class SensitiveHandler {
    private static final Map<Class<?>, SensitiveClassCache> CACHE = new ConcurrentHashMap<>();

    private static final DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();
    private static final DefaultObjectWrapperFactory defaultObjectWrapperFactory = new DefaultObjectWrapperFactory();
    private static final DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();

    @PostConstruct
    public void init() {

    }


    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    private SensitiveClassCache createCache(Object targetObj) {
        if (Objects.isNull(targetObj)) {
            return null;
        }
        Class<?> targetClass = targetObj.getClass();
        SensitiveClassCache cache = CACHE.get(targetClass);
        if (Objects.nonNull(cache)) {
            return cache;
        }
        synchronized (targetObj.getClass().getName().intern()) {
            cache = CACHE.get(targetClass);
            if (Objects.nonNull(cache)) {
                return cache;
            }

            SensitiveClassCache classCache = new SensitiveClassCache();
            classCache.setClazz(targetClass);
            List<Field> allFields = ReflectUtils.getFieldsWithSuper(targetObj);

            allFields.forEach(f -> {
                f.setAccessible(true);
                FieldSensitive annoData = f.getAnnotation(FieldSensitive.class);
                if (Objects.isNull(annoData)) {
                    return;
                }

                SensitiveClassCache.FieldOfBaseType fieldOfBaseType = new SensitiveClassCache.FieldOfBaseType();
                fieldOfBaseType.setField(f);
                fieldOfBaseType.setAnnoData(annoData);
                try {
                    fieldOfBaseType.setField(f);
                    fieldOfBaseType.setAnnoData(annoData);
                    classCache.getFields().add(fieldOfBaseType);
                } catch (Exception e1) {
                    log.error("error: ", e1);
                    throw new BizException("数据脱敏异常");
                }
            });
            log.info("sensitive handler, init class [{}]", targetClass.getSimpleName());
            CACHE.put(targetClass, classCache);
            return classCache;
        }
    }

    public void checkData(JSONObject data) {
        data.forEach((k, v) -> {
            if (v != null && String.valueOf(v).contains("*")) {
                throw new BizException("字段 {} 不能含有 * 字符", k);
            }
        });
    }

    /**
     * 还原对象
     *
     * 应用场景: 比如脱敏返回给前端后, 前端可能只改了a字段, 其他 b字段都是处于脱敏状态, 这时提交, 如果后端不处理, 就 <br/>
     * 把脱敏状态的数据也保存到数据库中了, eg: 如下数据, 所以你需要进行处理 <br/>
     * 前端提交的数据
     * <p>
     *   {
     *     "a": "测试一下",
     *     "b": "156****3220"
     *   }
     * </p>
     *
     * 该方法处理步骤如下: (目前只支持一级对象)
     * 1. 将明文对象进行脱敏, 生成一个脱敏对象
     * 2. 将新对象中每个字段和脱敏对象字段进行比对
     *  a. 如果两个字段一样(字段未经修改), 从明文对象中取出该字段赋值到新对象中
     *  b. 如果两个字段不一样样(字段有修改), 进行下一步
     *  c. 判断是否包含*, 从明文对象中取出该字段赋值到新对象中
     *
     * @apiNote 明文对象的字段不应该包含星号, 一般被脱敏的数据也不会包含星号字符
     * @param plaintextObj 明文对象
     * @param newObj 提交的新对象, 脱敏的字段值可能被更改
     */
    public <T> JSONObject restoreObj(T plaintextObj, T newObj) {
        if (newObj == null || plaintextObj instanceof Collection) {
            return newObj == null ? new JSONObject() : (JSONObject) JSON.toJSON(newObj);
        }
        JSONObject newJsonObj = (JSONObject) JSON.toJSON(newObj);
        if (plaintextObj == null) {
            checkData(newJsonObj);
            return newJsonObj;
        }
        JSONObject plaintextJsonObj = (JSONObject) JSON.toJSON(plaintextObj);
        handleField(plaintextObj);
        JSONObject sensitiveJsonObj = (JSONObject) JSON.toJSON(plaintextObj);
        SensitiveClassCache classCache = createCache(plaintextObj);

        Map<String, FieldSensitive> fieldMap = (classCache != null)
            ? classCache.getFields().stream().collect(Collectors.toMap(e -> e.getField().getName(), SensitiveClassCache.FieldOfBaseType::getAnnoData))
            : Collections.emptyMap();
        sensitiveJsonObj.forEach((k, v) -> {
            Object sensitiveValue = sensitiveJsonObj.get(k);
            Object newValue = newJsonObj.get(k);
            if (newValue == null) {
                return;
            }
            if (newValue.equals(sensitiveValue)) {
                newJsonObj.put(k, plaintextJsonObj.get(k));
                return;
            }

            if (newValue.toString().contains("*")) {
                String message = Optional.ofNullable(fieldMap.get(k)).map(FieldSensitive::desc).filter(StringUtils::isNotEmpty).orElse(k);
                throw new BizException("{} 不能包含 *", message);
            }
        });
        return newJsonObj;
    }

    @SuppressWarnings("unchecked")
    public void handleField(Object obj) {
        handleField(obj, new ConcurrentHashMap<>());
    }

    /**
     * 完成字段绑定
     */
    @SuppressWarnings("unchecked")
    private void handleField(Object obj, Map<Object, Class<?>> map) {
        if (Objects.isNull(obj)) {
            return;
        }
        if (obj instanceof Collection) {
            handlerCollectionField((Collection<Object>) obj, map);
        } else if (obj.getClass().isArray()) {
            handleArrayField(obj, map);
        } else if (obj instanceof Map) {
            handleMapField((Map<Object, Object>) obj, map);
        } else if (ClassUtil.isBasicType(obj.getClass()) || obj instanceof String) {
        } else {
            handleSingleField(obj, map);
        }
    }

    private void handleMapField(Map<Object, Object> objMap, Map<Object, Class<?>> map) {
        if (Objects.isNull(objMap)) {
            return;
        }
        objMap.forEach((key, value) -> handleField(value, map));
    }

    /**
     * 完成对象中字段绑定
     */
    private void handleObjectField(Object obj, Map<Object, Class<?>> map) {
        if (Objects.isNull(obj)) {
            return;
        }
        List<Field> fields = ReflectUtils.getFieldsWithSuper(obj);
        for (Field field : fields) {
            try {
                int modifiers = field.getModifiers();

                if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)
                        || Modifier.isInterface(modifiers) || Modifier.isAbstract(modifiers)) {
                    continue;
                }
                field.setAccessible(true);
                Object o = field.get(obj);
                handleField(o, map);
            } catch (Exception e) {
                log.error("error: ", e);
            }
        }
    }

    /**
     * 完成单字段绑定, eg:
     * class Test {
     * String name;
     * Long age;
     * }
     */
    private void handleSingleField(Object obj, Map<Object, Class<?>> map) {
        SensitiveClassCache classCache = CACHE.get(obj.getClass());
        if (Objects.isNull(classCache)) {
            classCache = createCache(obj);
            if (Objects.isNull(classCache)) {
                return;
            }
        }
        if (map.containsKey(obj)) {
            return;
        }
        map.put(obj, obj.getClass());

        MetaObject metaObject = MetaObject.forObject(obj, defaultObjectFactory, defaultObjectWrapperFactory, defaultReflectorFactory);
        doSensitiveFieldSet(obj, metaObject, classCache);
        handleObjectField(obj, map);
    }

    private void handleArrayField(Object obj, Map<Object, Class<?>> map) {
        if (Objects.isNull(obj) || map.containsKey(obj)) {
            return;
        }
        map.put(obj, obj.getClass());
        Object[] arr = (Object[]) obj;
        if (arr.length == 0) {
            return;
        }

        for (Object o : arr) {
            handleField(o, map);
        }
    }

    /**
     * 对集合中的每个对象做字段绑定处理
     */
    private void handlerCollectionField(Collection<Object> objs, Map<Object, Class<?>> map) {
        if (CollectionUtils.isEmpty(objs) || map.containsKey(objs)) {
            return;
        }
        map.put(objs, objs.getClass());
        objs.stream().filter(Objects::nonNull).findFirst()
                .ifPresent(e -> objs.parallelStream().forEach(o -> handleField(o, map)));
    }

    /**
     * 调度数据绑定接口
     *
     * @param metaObject 对象元数据
     * @param classCache 目标类信息缓存
     */
    private void doSensitiveFieldSet(Object obj, MetaObject metaObject, SensitiveClassCache classCache) {
        if (Objects.isNull(obj)) {
            return;
        }
        Set<SensitiveClassCache.FieldOfBaseType> fields = classCache.getFields();
        fields.forEach(fInfo -> {
            try {
                String name = fInfo.getField().getName();
                Class<?> type = fInfo.getField().getType();
                if (ClassUtil.isBasicType(type) || type.getName().equals("java.lang.String")) {
                    Object value = metaObject.getValue(name);
                    if (Objects.isNull(value)) {
                        return;
                    }
                    String valueStr = String.valueOf(value);
//                    if (valueStr.contains("**")) {
//                        throw new BizException("被脱敏的值 [{}] 不能包含 *", valueStr);
//                    }
                    FieldSensitive annoData = fInfo.getAnnoData();
                    if (Objects.equals(annoData.strategy(), StrategyType.CUSTOM)) {
                        String ret = StringUtils.str2Star(valueStr, annoData.frontNum(), annoData.endNum(), annoData.starNum());
                        metaObject.setValue(name, ret);
                        return;
                    }

                    Function<String, Object> strategyProcessor = SensitiveConfig.getInstance().getStrategyProcessor(fInfo.getAnnoData().strategy());
                    metaObject.setValue(name, strategyProcessor.apply(valueStr));
                    return;
                }
                log.warn("class [{}] field [{}] not is base type", type.getName(), name);
            } catch (BizException e) {
                throw e;
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}
